package top.cardone.web.support;

import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.gson.Gson;
import groovy.lang.GroovyObject;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import top.cardone.cache.Cache;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.context.util.CodeExceptionUtils;
import top.cardone.context.util.TableUtils;
import top.cardone.core.CodeException;
import top.cardone.core.util.action.Action1;
import top.cardone.core.util.func.Func1;
import top.cardone.core.util.func.Func2;
import top.cardone.core.util.func.Func3;
import top.cardone.data.support.ExcelSupport;
import top.cardone.mapper.BeanMapper;
import top.cardone.validator.Validator;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;

/**
 * @author yao hai tao
 * @date 16-3-23
 */
@Log4j2
public class WebSupport {
    /**
     * api 静态根目录
     */
    @Setter
    @Getter
    private String apiStaticRoot;

    /**
     * 多个对象键名
     */
    @Setter
    private String objectsKey = "datas";

    /**
     * 单个基单类型键名
     */
    @Setter
    private String objectKey = "data";

    /**
     * 输出
     */
    @Setter
    private String outputConfigKey = "output";

    /**
     * 验证
     */
    @Setter
    private String validationConfigKey = "validation";

    /**
     * func
     */
    @Setter
    private String funcConfigKey = "func";

    /**
     * 输入
     */
    @Setter
    private String inputConfigKey = "input";

    @Setter
//    @Value("${top.cardone.web.support.WebSupport.uploadPath:file:upload}")
    private String uploadPath = "file:upload";

    @Setter
//    @Value("${top.cardone.web.support.WebSupport.defaultJsonString:{\"errorCode\": \"Call failed: interface that does not exist\", \"error\": \"调用失败:不存在的接口\"}}")
    private String defaultJsonString = "{\"errorCode\": \"Call failed: interface that does not exist\", \"error\": \"调用失败:不存在的接口\"}";

    @Setter
    @Getter
    private List<String> apiConfigProjectRoots = Lists.newArrayList();

    @Setter
    private String cacheBeanName = "cardone.web.cache";

    @Setter
    private Map<String, String> servletPathMap = Maps.newConcurrentMap();

    public void download(HttpServletRequest request, HttpServletResponse response, String filePath, String downloadFileName) throws Exception {
        Resource resource = ApplicationContextHolder.getResource(filePath);

        if (!resource.exists()) {
            throw new CodeException("download file don't exist", "下载文件不存在：" + filePath);
        }

        response.setContentType("application/octet-stream");
        response.setHeader("content-disposition", "attachment; filename=" + URLEncoder.encode(downloadFileName, "UTF-8"));

        try (java.io.OutputStream out = response.getOutputStream()) {
            try (java.io.InputStream in = resource.getInputStream()) {
                IOUtils.write(IOUtils.toByteArray(in), out);
            }

            out.flush();

            response.flushBuffer();
        }
    }

    public void exportExcel(HttpServletRequest request,
                            HttpServletResponse response,
                            String excelTemplateFilePath,
                            String exportFileName,
                            Func1<Page<Map<String, Object>>,
                                    Map<String, Object>> func) {
        this.exportExcel(request, response, !log.isDebugEnabled(), excelTemplateFilePath, exportFileName, func, null);
    }

    public void exportExcel(HttpServletRequest request,
                            HttpServletResponse response,
                            boolean cache,
                            String excelTemplateFilePath,
                            String exportFileName,
                            Func1<Page<Map<String, Object>>, Map<String, Object>> func) {
        this.exportExcel(request, response, cache, excelTemplateFilePath, exportFileName, func, null);
    }

    public void exportExcel(HttpServletRequest request,
                            HttpServletResponse response,
                            boolean cache,
                            String excelTemplateFilePath,
                            String exportFileName,
                            Func1<Page<Map<String, Object>>, Map<String, Object>> func,
                            Action1<Table<String, String, Object>> initConfigAction) {
        Resource excelTemplateResource = ApplicationContextHolder.getResource(excelTemplateFilePath);

        if (!excelTemplateResource.exists()) {
            throw new CodeException("template file don't exist", "模板文件不存在：" + excelTemplateFilePath);
        }

        Map<String, Object> context = Maps.newHashMap();

        try {
            context.put("request", request);
            context.put("response", response);

            String requestMethodName = getRequestMethodName(request);

            String mappingServletPath = getMappingServletPath(request.getServletPath(), requestMethodName);

            context.put("servletPath", mappingServletPath);

            context.put("requestMethod", requestMethodName);

            if (ApplicationContextHolder.getApplicationContext().containsBean(mappingServletPath)) {
                context.put("func", ApplicationContextHolder.getBean(Func1.class, mappingServletPath));
            }

            context.put("config", this.getConfig(mappingServletPath, requestMethodName, cache));

            context.put("groovyObject", this.getGroovyObject(mappingServletPath, requestMethodName, cache));

            Map<Object, Table<String, String, Object>> configTableMap = Maps.newHashMap();

            context.put("funcFunc", (Func1<Object, Map<String, Object>>) input -> {
                if (!input.containsKey("pageSize")) {
                    input.put("pageSize", 20000);
                } else {
                    if (MapUtils.getInteger(input, "pageSize", 0) > 60000) {
                        input.put("pageSize", 60000);
                    }
                }

                int index = 0;

                do {
                    //调用接口
                    Page<Map<String, Object>> page = null;

                    input.put("pageNumber", index + 1);

                    if (Objects.nonNull(func)) {
                        page = func.func(input);
                    } else if (Objects.nonNull(context.get("groovyObject"))) {
                        page = (Page<Map<String, Object>>) ((GroovyObject) context.get("groovyObject")).invokeMethod(funcConfigKey, new Object[]{input});
                    }

                    if (page == null || CollectionUtils.isEmpty(page.getContent())) {
                        break;
                    }

                    Map<String, Object> config = MapUtils.getMap(MapUtils.getMap(context, "config"), "exportExcel");

                    Table<String, String, Object> configTable = TableUtils.newTable(config);

                    if (initConfigAction != null) {
                        initConfigAction.action(configTable);
                    }

                    context.put("output", page.getContent());

                    configTable.put("data", "writeList", this.output(context, cache));

                    // 根据 sheet 索引去定位 sheet
                    configTableMap.put(index++, configTable);

                    if (!page.hasNext()) {
                        break;
                    }

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        log.error(e);
                    }
                } while (true);

                return null;
            });

            context.put("outputFunc", (Func1<Object, Map<String, Object>>) input -> {
                try (InputStream templateFileInputStream = excelTemplateResource.getInputStream()) {
                    ApplicationContextHolder.getBean(ExcelSupport.class).writeHttpServletResponse(response, exportFileName, templateFileInputStream, configTableMap);
                } catch (Exception e) {
                    log.error(e);
                }

                return null;
            });

            this.func(context, cache);
        } finally {
            if (context != null) {
                context.clear();
            }
        }

    }

    public Object importExcel(HttpServletRequest request, HttpServletResponse response, Func1<Object, List<Map<String, Object>>> func) throws Exception {
        return this.importExcel(request, response, !log.isDebugEnabled(), func, null);
    }

    public Object importExcel(HttpServletRequest request, HttpServletResponse response, boolean cache, Func1<Object, List<Map<String, Object>>> func) throws Exception {
        return this.importExcel(request, response, cache, func, null);
    }

    public Object importExcel(HttpServletRequest request,
                              HttpServletResponse response,
                              boolean cache,
                              Func1<Object, List<Map<String, Object>>> func,
                              Action1<Table<String, String, Object>> initConfigAction) throws Exception {
        final MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;

        if (multipartRequest == null) {
            throw new CodeException("upload file don't exist", "没有上传文件");
        }

        Collection<MultipartFile> multipartFiles = multipartRequest.getFileMap().values();

        if (org.springframework.util.CollectionUtils.isEmpty(multipartFiles)) {
            throw new CodeException("upload file don't exist", "没有上传文件");
        }

        Map<String, Object> context = Maps.newHashMap();

        try {
            context.put("request", request);
            context.put("response", response);

            String requestMethodName = getRequestMethodName(request);

            String mappingServletPath = getMappingServletPath(request.getServletPath(), requestMethodName);

            context.put("servletPath", mappingServletPath);

            context.put("requestMethod", requestMethodName);

            context.put("func", func);

            context.put("config", this.getConfig(mappingServletPath, requestMethodName, cache));

            context.put("groovyObject", this.getGroovyObject(mappingServletPath, requestMethodName, cache));

            Func3<Boolean, Map<String, Object>, Integer, Table<String, String, Object>> validateFunc = (rowData, integer, stringStringObjectTable) -> {
                try {
                    context.put("input", rowData);

                    this.validation(context);

                    return true;
                } catch (CodeException e) {
                    String errorKey = rowData.containsKey("error") ? "error_message" : "error";

                    rowData.put(errorKey, MapUtils.getString(CodeExceptionUtils.newMap(request.getServletPath(), e), "error", e.getErrorCode()));

                    return false;
                }
            };

            Map<Object, Table<String, String, Object>> configTableMap = Maps.newHashMap();

            Map<String, Object> importConfig = MapUtils.getMap(MapUtils.getMap(context, "config"), "importConfig");

            int minSheelIndex = MapUtils.getIntValue(importConfig, "minSheelIndex", 0);
            int maxSheelIndex = MapUtils.getIntValue(importConfig, "maxSheelIndex", 9);

            for (int i = minSheelIndex; i < maxSheelIndex; i++) {
                Map<String, Object> importExcelConfig = MapUtils.getMap(MapUtils.getMap(context, "config"), "importExcel");

                Table<String, String, Object> configTable = TableUtils.newTable(importExcelConfig);

                if (initConfigAction != null) {
                    initConfigAction.action(configTable);
                }

                configTable.put("func", "validateFunc", validateFunc);

                // 根据 sheet 名称去定位 sheet
                configTableMap.put(i, configTable);
            }

            List<Map<String, Object>> outputs = Lists.newArrayList();

            context.put("outputs", outputs);

            for (MultipartFile multipartFile : multipartFiles) {
                String tempUpload;

                if ("/".equals(File.separator)) {
                    tempUpload = top.cardone.context.util.StringUtils.remove(uploadPath, "file:");
                } else {
                    tempUpload = top.cardone.context.util.StringUtils.remove(uploadPath, "file:/");
                }

                tempUpload += "/" + UUID.randomUUID() + "/";

                File uploadFile = null;

                try {
                    String uploadFilePathName = tempUpload + multipartFile.getOriginalFilename();

                    uploadFile = new File(uploadFilePathName);

                    FileUtils.writeByteArrayToFile(uploadFile, multipartFile.getBytes());

                    ApplicationContextHolder.getBean(ExcelSupport.class).readFile(uploadFile, configTableMap);

                    for (Map.Entry<Object, Table<String, String, Object>> tableEntry : configTableMap.entrySet()) {
                        List<Map<String, Object>> dataList = (List<Map<String, Object>>) tableEntry.getValue().get("data", "dataList");

                        if (CollectionUtils.isEmpty(dataList)) {
                            continue;
                        }

                        Map<String, Object> output = Maps.newHashMap();

                        outputs.add(output);

                        List<Map<String, Object>> succeedDataList = (List<Map<String, Object>>) tableEntry.getValue().get("data", "succeedDataList");

                        output.put("dataList", dataList);
                        output.put("succeedDataList", succeedDataList);
                        output.put("errorDataList", tableEntry.getValue().get("data", "errorDataList"));

                        if (CollectionUtils.isEmpty(succeedDataList)) {
                            continue;
                        }

                        succeedDataList = (List<Map<String, Object>>) ((GroovyObject) context.get("groovyObject")).invokeMethod(this.inputConfigKey, new Object[]{succeedDataList});

                        if (Objects.nonNull(func)) {
                            func.func(succeedDataList);
                        } else if (Objects.nonNull(context.get("groovyObject"))) {
                            ((GroovyObject) context.get("groovyObject")).invokeMethod(this.funcConfigKey, new Object[]{succeedDataList});
                        }
                    }
                } finally {
                    FileUtils.deleteQuietly(uploadFile);

                    FileUtils.deleteQuietly(new File(tempUpload));
                }
            }

            return this.output(context, cache);
        } finally {
            if (context != null) {
                context.clear();
            }
        }
    }

    private String getRequestMethodName(HttpServletRequest request) {
        return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, StringUtils.lowerCase(request.getMethod()));
    }

    public void validation(Map<String, Object> context) {
        String validationConfigKey = MapUtils.getString(context, "validationConfigKey", this.validationConfigKey);

        if (Objects.nonNull(context.get("config"))) {
            Map<String, Object> config = MapUtils.getMap(MapUtils.getMap(context, "config"), validationConfigKey);

            if (MapUtils.isNotEmpty(config)) {
                Map<String, Object> errorMap = ApplicationContextHolder.getBean(Validator.class).validate(config, (Map<String, Object>) context.get("input"));

                if (top.cardone.context.util.MapUtils.isNotEmpty(errorMap)) {
                    for (Map.Entry<String, Object> errorEntry : errorMap.entrySet()) {
                        if (errorEntry.getValue() instanceof Map) {
                            Map<String, Object> valueMap = (Map) errorEntry.getValue();

                            throw new CodeException(errorEntry.getKey(), (Object[]) MapUtils.getObject(valueMap, "args"), MapUtils.getString(valueMap, "defaultMessage"), null);
                        }

                        throw new CodeException(errorEntry.getKey(), (String) errorEntry.getValue());
                    }
                }
            }
        }

        if (Objects.nonNull(context.get("groovyObject"))) {
            ((GroovyObject) context.get("groovyObject")).invokeMethod(validationConfigKey, new Object[]{context.get("input")});
        }
    }

    public Object input(Map<String, Object> context) {
        Map<String, Object> input;

        if (Objects.nonNull(context.get("request-input"))) {
            input = ApplicationContextHolder.getBean(BeanMapper.class).getObject(Map.class, context.get("request-input"));
        } else {
            input = this.getObject(Map.class, ((HttpServletRequest) context.get("request")));
        }

        if (log.isDebugEnabled()) {
            log.debug(context.get("servletPath") + "\r\ninput\r\n" + ApplicationContextHolder.getBean(Gson.class).toJson(input));
        }

        String inputConfigKey = MapUtils.getString(context, "inputConfigKey", this.inputConfigKey);

        if (Objects.nonNull(context.get("config"))) {
            Map<String, Object> config = MapUtils.getMap(MapUtils.getMap(context, "config"), inputConfigKey);

            if (MapUtils.isNotEmpty(config)) {
                return ApplicationContextHolder.getBean(BeanMapper.class).getObject(Map.class, input, config);
            }
        }

        if (Objects.nonNull(context.get("groovyObject"))) {
            return ((GroovyObject) context.get("groovyObject")).invokeMethod(inputConfigKey, new Object[]{input});
        }

        return input;
    }

    public Object output(Map<String, Object> context, boolean cache) {
        String outputConfigKey = MapUtils.getString(context, "outputConfigKey", this.outputConfigKey);

        Object output = context.containsKey("outputs") ? context.get("outputs") : context.get("output");

        if (Objects.nonNull(context.get("config"))) {
            Map<String, Object> config = MapUtils.getMap(MapUtils.getMap(context, "config"), outputConfigKey);

            if (MapUtils.isNotEmpty(config)) {
                if (output instanceof List) {
                    List<Map<String, Object>> newList = Lists.newArrayList();

                    for (Object obj : (List) output) {
                        newList.add(top.cardone.context.util.MapUtils.newHashMap(top.cardone.context.util.MapUtils.toMap(obj), config));
                    }

                    if (log.isDebugEnabled()) {
                        log.debug("return\r\n" + ApplicationContextHolder.getBean(Gson.class).toJson(newList));
                    }

                    return this.output(context, newList, cache);
                } else {
                    Map<String, Object> newData = top.cardone.context.util.MapUtils.toMap(output, this.objectsKey, this.objectKey);

                    if (log.isDebugEnabled()) {
                        log.debug("newData\r\n" + ApplicationContextHolder.getBean(Gson.class).toJson(newData));
                    }

                    Object obj = top.cardone.context.util.MapUtils.newHashMap(newData, config);

                    if (log.isDebugEnabled()) {
                        log.debug("return\r\n" + ApplicationContextHolder.getBean(Gson.class).toJson(obj));
                    }

                    return this.output(context, obj, cache);
                }
            }
        }

        if (Objects.nonNull(context.get("groovyObject"))) {
            return this.output(context, ((GroovyObject) context.get("groovyObject")).invokeMethod(outputConfigKey, new Object[]{output}), cache);
        }

        return this.output(context, output, cache);
    }

    public Object output(Map<String, Object> context, Object output, boolean cache) {
        if (output == null) {
            HttpServletRequest request = (HttpServletRequest) MapUtils.getObject(context, "request");

            HttpServletResponse response = (HttpServletResponse) MapUtils.getObject(context, "response");

            String servletPath = MapUtils.getString(context, "servletPath");

            return this.readApiStaticFile(request, response, servletPath, cache);
        } else {
            return output;
        }
    }

    public Object func(HttpServletRequest request, String servletPath, boolean cache) {
        return this.func(request, Maps.newHashMap(), servletPath, cache);
    }

    public Object func(HttpServletRequest request, HttpServletResponse response, String servletPath, boolean cache) {
        return this.func(request, response, Maps.newHashMap(), servletPath, cache);
    }

    public Object func(HttpServletRequest request, Map<String, Object> context, String servletPath, boolean cache) {
        String requestMethodName = getRequestMethodName(request);

        String mappingServletPath = getMappingServletPath(servletPath, requestMethodName);

        GroovyObject groovyObject = this.getGroovyObject(mappingServletPath, requestMethodName, cache);

        if (groovyObject instanceof Func1) {
            return ((Func1) groovyObject).func(request);
        }

        context.put("request", request);

        context.put("servletPath", mappingServletPath);

        context.put("requestMethod", requestMethodName);

        if (ApplicationContextHolder.getApplicationContext().containsBean(mappingServletPath)) {
            context.put("func", ApplicationContextHolder.getBean(Func1.class, mappingServletPath));
        }

        context.put("config", this.getConfig(mappingServletPath, requestMethodName, cache));

        context.put("groovyObject", groovyObject);

        return this.func(context, cache);
    }

    public Object func(HttpServletRequest request, HttpServletResponse response, Map<String, Object> context, String servletPath, boolean cache) {
        String requestMethodName = getRequestMethodName(request);

        String mappingServletPath = getMappingServletPath(servletPath, requestMethodName);

        GroovyObject groovyObject = this.getGroovyObject(mappingServletPath, requestMethodName, cache);

        if (groovyObject instanceof Func2) {
            return ((Func2) groovyObject).func(request, response);
        }

        if (groovyObject instanceof Func1) {
            return ((Func1) groovyObject).func(request);
        }

        context.put("request", request);

        context.put("response", response);

        context.put("servletPath", mappingServletPath);

        context.put("requestMethod", requestMethodName);

        if (ApplicationContextHolder.getApplicationContext().containsBean(mappingServletPath)) {
            context.put("func", ApplicationContextHolder.getBean(Func1.class, mappingServletPath));
        }

        context.put("config", this.getConfig(mappingServletPath, requestMethodName, cache));

        context.put("groovyObject", groovyObject);

        return this.func(context, cache);
    }

    public Object func(HttpServletRequest request, Func1<Object, Map<String, Object>> func) {
        return this.func(request, func, !log.isDebugEnabled());
    }

    public Object func(HttpServletRequest request, Func1<Object, Map<String, Object>> func, boolean cache) {
        Map<String, Object> context = Maps.newHashMap();

        try {
            context.put("request", request);

            String requestMethodName = getRequestMethodName(request);

            String mappingServletPath = getMappingServletPath(request.getServletPath(), requestMethodName);

            context.put("servletPath", mappingServletPath);

            context.put("requestMethod", requestMethodName);

            context.put("func", func);

            context.put("config", this.getConfig(mappingServletPath, requestMethodName, cache));

            context.put("groovyObject", this.getGroovyObject(mappingServletPath, requestMethodName, cache));

            return this.func(context, cache);
        } finally {
            if (context != null) {
                context.clear();
            }
        }
    }

    public Object func(Map<String, Object> context, boolean cache) {
        if (Objects.nonNull(context.get("funcFunc")) ||
                Objects.nonNull(context.get("func")) ||
                Objects.nonNull(context.get("groovyObject")) ||
                Objects.nonNull(context.get("outputFunc"))) {
            if (Objects.isNull(context.get("input"))) {
                if (Objects.nonNull(context.get("inputFunc"))) {
                    context.put("input", ((Func1) context.get("inputFunc")).func(context));
                } else {
                    context.put("input", this.input(context));
                }
            }

            this.validation(context);
        }

        if (Objects.nonNull(context.get("funcFunc"))) {
            context.put("output", ((Func1) context.get("funcFunc")).func(context.get("input")));
        } else if (Objects.nonNull(context.get("func"))) {
            context.put("output", ((Func1) context.get("func")).func(context.get("input")));
        } else if (Objects.nonNull(context.get("groovyObject"))) {
            context.put("output", ((GroovyObject) context.get("groovyObject")).invokeMethod(this.funcConfigKey, new Object[]{context.get("input")}));
        } else {
            context.put("output", context.get("input"));
        }

        if (Objects.nonNull(context.get("outputFunc"))) {
            return ((Func1) context.get("outputFunc")).func(context.get("output"));
        }

        return this.output(context, cache);
    }

    private String getMappingServletPath(String servletPath, String requestMethod) {
        if (MapUtils.isEmpty(servletPathMap)) {
            return servletPath;
        }

        String mappingServletPath = servletPathMap.get(servletPath + ":" + requestMethod);

        if (StringUtils.isBlank(mappingServletPath)) {
            return servletPath;
        }

        String[] mapServletPaths = mappingServletPath.split(",");

        return StringUtils.defaultString(top.cardone.context.util.StringUtils.getPathForMatch(Lists.newArrayList(mapServletPaths), servletPath), servletPath);
    }


    private String getApiStaticFilePath(String servletPath, String requestMethod) {
        String apiStaticJsonFilePath = servletPath;

        if (StringUtils.isNotBlank(requestMethod)) {
            apiStaticJsonFilePath += "/" + requestMethod;
        }

        return apiStaticJsonFilePath;
    }

    private String getApiConfigJsonFilePath(String servletPath, String requestMethod) {
        String apiConfigJsonFilePath = servletPath;

        if ("json".equals(FilenameUtils.getExtension(apiConfigJsonFilePath))) {
            apiConfigJsonFilePath = FilenameUtils.removeExtension(apiConfigJsonFilePath);
        }

        if (StringUtils.isNotBlank(requestMethod)) {
            apiConfigJsonFilePath += "/" + requestMethod;
        }

        apiConfigJsonFilePath += ".json";

        return apiConfigJsonFilePath;
    }

    private String getGroovyFilePath(String servletPath, String requestMethod) {
        String groovyFilePath = servletPath;

        if ("json".equals(FilenameUtils.getExtension(groovyFilePath))) {
            groovyFilePath = FilenameUtils.removeExtension(groovyFilePath);
        }

        if (StringUtils.isNotBlank(requestMethod)) {
            groovyFilePath += "/" + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, requestMethod) + "Func";
        }

        groovyFilePath += ".groovy";

        return groovyFilePath;
    }

    public GroovyObject getGroovyObject(String servletPath, String requestMethod, boolean cache) {
        String[] scriptNames = new String[]{
                this.getGroovyFilePath(servletPath, requestMethod),
                this.getGroovyFilePath(servletPath, null)
        };

        GroovyObject groovyObject = ApplicationContextHolder.getBean(apiConfigProjectRoots, scriptNames, cache);

        return groovyObject;
    }

    private String getApiConfigProjectRoot(String filePath, boolean cache) {
        if (cache) {
            return ApplicationContextHolder.getBean(Cache.class, this.cacheBeanName).get(BeanMapper.class.getName(), 1, filePath, () -> this.getApiConfigProjectRoot(filePath));
        }

        return this.getApiConfigProjectRoot(filePath);
    }

    private String getApiConfigProjectRoot(String filePath) {
        if (CollectionUtils.isEmpty(apiConfigProjectRoots)) {
            return StringUtils.EMPTY;
        }

        for (String apiConfigProjectRoot : apiConfigProjectRoots) {
            String newFilePath = apiConfigProjectRoot + filePath;

            Resource resource = ApplicationContextHolder.getResource(newFilePath);

            if (resource.exists()) {
                return apiConfigProjectRoot;
            }
        }

        return StringUtils.EMPTY;
    }

    public Map<String, Object> getConfig(String servletPath, String requestMethod, boolean cache) {
        String apiConfigJsonFilePath = this.getApiConfigJsonFilePath(servletPath, requestMethod);

        String apiConfigProjectRoot = this.getApiConfigProjectRoot(apiConfigJsonFilePath, cache);

        if (StringUtils.isBlank(apiConfigProjectRoot)) {
            apiConfigJsonFilePath = this.getApiConfigJsonFilePath(servletPath, null);

            apiConfigProjectRoot = this.getApiConfigProjectRoot(apiConfigJsonFilePath, cache);

            if (StringUtils.isBlank(apiConfigProjectRoot)) {
                return null;
            }
        }

        if (cache) {
            return ApplicationContextHolder.getBean(BeanMapper.class).getObjectByFilePathCache(Map.class, apiConfigProjectRoot + apiConfigJsonFilePath);
        }

        return ApplicationContextHolder.getBean(BeanMapper.class).getObjectByFilePath(Map.class, apiConfigProjectRoot + apiConfigJsonFilePath);
    }

    public Object readApiStaticFile(HttpServletRequest request, HttpServletResponse response, boolean cache) {
        String requestMethodName = getRequestMethodName(request);

        String mappingServletPath = getMappingServletPath(request.getServletPath(), requestMethodName);

        return this.readApiStaticFile(request, response, mappingServletPath, cache);
    }

    public Object readApiStaticFile(HttpServletRequest request, HttpServletResponse response, String servletPath, boolean cache) {
        String contentType = StringUtils.defaultString(request.getContentType(), request.getHeader("Accept"));

        boolean returnString = top.cardone.context.util.StringUtils.startsWith(contentType, MediaType.APPLICATION_JSON_VALUE);

        if (returnString && cache) {
            String requestMethodName = getRequestMethodName(request);

            return ApplicationContextHolder
                    .getBean(Cache.class, this.cacheBeanName)
                    .get(this.getClass().getName() + ".readApiStaticFileToString", 1, servletPath + ":" + requestMethodName, () -> this.readApiStaticFile(request, response, returnString, servletPath));
        }

        return this.readApiStaticFile(request, response, returnString, servletPath);
    }

    private Object readApiStaticFile(HttpServletRequest request, HttpServletResponse response, boolean returnString, String servletPath) {
        if (StringUtils.isBlank(apiStaticRoot)) {
            log.error("api static root is blank");

            return defaultJsonString;
        }

        String requestMethodName = getRequestMethodName(request);

        try {
            List<String> apiStaticFilePaths = Lists.newArrayList();

            String requestMethodNameServletPath = apiStaticRoot + getApiStaticFilePath(servletPath, requestMethodName);

            apiStaticFilePaths.add(requestMethodNameServletPath);
            apiStaticFilePaths.add(requestMethodNameServletPath + ".json");
            apiStaticFilePaths.add(apiStaticRoot + servletPath);
            apiStaticFilePaths.add(apiStaticRoot + servletPath + ".json");

            Resource resource = null;

            for (String apiStaticFilePath : apiStaticFilePaths) {
                resource = ApplicationContextHolder.getResource(apiStaticFilePath);

                if (resource.exists()) {
                    break;
                }
            }

            if (resource == null || !resource.exists()) {
                log.debug("api static file exists:" + apiStaticFilePaths);

                return defaultJsonString;
            }

            log.debug("api static file:" + apiStaticFilePaths);

            if (returnString) {
                try (InputStream is = resource.getInputStream()) {
                    String jsonString = IOUtils.toString(is, Charsets.UTF_8);

                    return jsonString;
                }
            }

            try (InputStream is = resource.getInputStream()) {
                try (ServletOutputStream os = response.getOutputStream()) {
                    IOUtils.copy(is, os, 1024 * 1024);
                }
            }

            return null;
        } catch (IOException e) {
            throw new CodeException("read api static file error", new String[]{e.getMessage()}, e);
        }
    }

    /**
     * 获取对象
     *
     * @param cls
     * @param request
     * @param <T>
     * @return
     */
    public <T> T getObject(Class<T> cls, HttpServletRequest request) {
        if (StringUtils.startsWith(request.getContentType(), org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
            return this.getObjectByJson(cls, request);
        }

        return this.getObjectByParameter(cls, request);
    }

    public final static ThreadLocal<String> INPUT_STREAM_STRING_THREAD_LOCAL = ThreadLocal.withInitial(() -> null);

    /**
     * 获取对象
     *
     * @param cls
     * @param request
     * @param <T>
     * @return
     */
    public <T> T getObjectByJson(Class<T> cls, HttpServletRequest request) {
        if (Objects.nonNull(INPUT_STREAM_STRING_THREAD_LOCAL.get())) {
            return ApplicationContextHolder.getBean(BeanMapper.class).getObject(cls, INPUT_STREAM_STRING_THREAD_LOCAL.get()
                    , org.springframework.web.util.WebUtils.getParametersStartingWith(request, null));
        }

        try (java.io.InputStream is = request.getInputStream()) {
            INPUT_STREAM_STRING_THREAD_LOCAL.set(StringUtils.trim(IOUtils.toString(is, request.getCharacterEncoding())));

            return ApplicationContextHolder.getBean(BeanMapper.class).getObject(cls, INPUT_STREAM_STRING_THREAD_LOCAL.get()
                    , org.springframework.web.util.WebUtils.getParametersStartingWith(request, null));
        } catch (IOException e) {
            throw new CodeException("api get object json error", new String[]{e.getMessage()}, e);
        }
    }

    /**
     * 获取对象
     *
     * @param cls
     * @param request
     * @param <T>
     * @return
     */
    public <T> T getObjectByParameter(Class<T> cls, HttpServletRequest request) {
        return ApplicationContextHolder.getBean(BeanMapper.class).getObject(cls, org.springframework.web.util.WebUtils.getParametersStartingWith(request, null));
    }
}
